﻿using System;
using System.Linq.Expressions;
using System.Reflection;

namespace Loki
{
    public static class ExpressionHelper
    {
        /// <summary>
        /// Provides strong-typed reflection of the <typeparamref name="TTarget"/> .
        /// type.
        /// </summary>
        /// <typeparam name="TTarget">Type to reflect.</typeparam>
        public static class Reflect<TTarget>
        {
            /// <summary>
            /// Gets the property represented by the lambda expression.
            /// </summary>
            /// <param name="property">An expression that accesses a property.</param>
            /// <exception cref="ArgumentNullException">The <paramref name="method"/> is null.</exception>
            /// <exception cref="ArgumentException">The <paramref name="method"/> is not a lambda expression or it does not represent a property access.</exception>
            /// <returns>The property info.</returns>
            public static PropertyInfo GetProperty<TPropertyType>(Expression<Func<TTarget, TPropertyType>> property)
            {
                PropertyInfo info = GetMemberInfo(property) as PropertyInfo;
                if (info == null)
                {
                    throw new ArgumentException("Member is not a property");
                }

                return info;
            }

            private static MethodInfo GetMethodInfo(Expression method)
            {
                if (method == null)
                {
                    throw new ArgumentNullException("method");
                }

                LambdaExpression lambda = method as LambdaExpression;
                if (lambda == null)
                {
                    throw new ArgumentException("Not a lambda expression", "method");
                }

                if (lambda.Body.NodeType != ExpressionType.Call)
                {
                    throw new ArgumentException("Not a method call", "method");
                }

                return ((MethodCallExpression)lambda.Body).Method;
            }

            private static MemberInfo GetMemberInfo(Expression member)
            {
                if (member == null)
                {
                    throw new ArgumentNullException("member");
                }

                LambdaExpression lambda = member as LambdaExpression;
                if (lambda == null)
                {
                    throw new ArgumentException("Not a lambda expression", "member");
                }

                MemberExpression memberExpr = null;

                // The Func<TTarget, object> we use returns an object, so first statement can be either 
                // a cast (if the field/property does not return an object) or the direct member access.
                if (lambda.Body.NodeType == ExpressionType.Convert)
                {
                    // The cast is an unary expression, where the operand is the 
                    // actual member access expression.
                    memberExpr = ((UnaryExpression)lambda.Body).Operand as MemberExpression;
                }
                else if (lambda.Body.NodeType == ExpressionType.MemberAccess)
                {
                    memberExpr = lambda.Body as MemberExpression;
                }

                if (memberExpr == null)
                {
                    throw new ArgumentException("Not a member access", "member");
                }

                return memberExpr.Member;
            }
        }

        public static Expression<Func<TTarget>> New<TTarget>()
        {
            Type L_Type = typeof(TTarget);

            ConstructorInfo L_builder = L_Type.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public, Type.DefaultBinder, Type.EmptyTypes, null);
            if (L_builder == null)
            {
                throw new ArgumentException(string.Format("No parameterless constructor for L_Type {0}", L_Type.FullName));
            }

            return Expression.Lambda<Func<TTarget>>(Expression.New(L_builder));
        }

        public static bool HasDefaultConstructor(Type P_Type)
        {
            if (P_Type.IsPrimitive)
            {
                return true;
            }

            if (P_Type.GetConstructor(BindingFlags.Public | BindingFlags.Instance | BindingFlags.CreateInstance, null, new Type[0], null) != null)
            {
                return true;
            }

            return false;
        }
    }
}
